---
description: >
  Everyday accessibility for HTML coders.
title: Web Accessibility
---

> Make it accessible.
>
> Make it fancy.
>
> Make sure the fancy doesn't break accessibility.
>
> &mdash; <cite>Morten Rand-Hendriksen</cite>

**TLDR;** _See how accessibility helps improve all user's experience.
[WAI's "bad" demo](https://www.w3.org/WAI/demos/bad/){:rel="nofollow noopener noreferrer"}
is a great way to learn about accessibility by example._

<details markdown="1" id="table-of-contents">
<summary>
Table of Contents
</summary>

* TOC
{:toc}
</details>

## POUR Principles

Ensure content is always:

- Perceivable. Available to sight, hearing, and/or touch.
- Operable. Usable forms, controls, and navigation.
- Understandable. UI shows intent.
- Robust. ie. Assistive technologies friendly.

## Usability

- Support keyboard navigation, and interaction as much as on the mouse.
- Show user location when using keyboard navigation.
- Add links to opt-out keyboard navigation on certain sections. eg. `nav` bars.

## Structured Pages

**NOTE** _Code examples are in [Slim](https://www.rubydoc.info/gems/slim/frames){:rel="nofollow noopener noreferrer"}
to keep the focus on the elements, attributes, and values rather than HTML's
verbosity._

- Use HTML5 semantically.
- Set descriptive `title` meta tag for user agents.
- `section` groups elements into units of information.
- Use `div` primarily for:
  + Grouping what should be styled together
  + Enforcing layouts, instead of being their foundation.
- `header` for page headlines, and occasionally for `section`s.
- `nav` for major navigation links for a site. Often a `header` or `aside` child.
- `footer` for info about a page, or `section` eg. copyright.
- `aside` Independent info related to the surrounding content. eg. sidebar, pull-quotes.
- `section` Content-focused element for grouping independently consumable parts of a page.
- `article` Self-contained content that could be consumed independently from a page.
- Style headings(`h`X), don't skip them.
- Set text tone with
  + `strong` to add / show importance.
  + `em` for emphasis. ie. tone.
- Id content in different languages with the `lang` attribute.
- Style text inline.
  + `mark` to highlight content.
  + `small` for small print.
  + `abbr` for abbreviations.
  + `span` for any other styling. eg. icon fonts. (`i` doesn't stand for icon.)

Beware that some elements, such as `abbr` we'll need to set complimentary
attributes to make full use of them.

```html
<abbr title="Accessible Rich Internet Applications">ARIA</abbr>
```

Although, the `title` property on `abbr` is well supported on by screen readers,
we shouldn't rely on it for any other elements.

In general, a page's header and headings should make a nice table of contents.
(Comments beginning with `//` refer to the line below.)

```slim
doctype 5
html lang="en" / Language codes linked in the references
  head
    meta charset="UTF-8" / Help display text properly
    // Use only when site is responsive
    meta name="viewport" content="width=device-width, initial-scale=1.0"
    // Tell Microsoft's browers we expect them to behave like others
    meta http-equiv="X-UA-Compatible" content="IE=Edge"
    title Short & Meaningful for screen readers
    meta name="description" content="This text shows up in search engines"
  body
    header role="banner" / Role for main header only
    nav role="navigation"
    main role="main"
      section
        article
          header
            h1 Title
          form role="form"
            / ... forms are covered below
          p
            | This is how we'll
            em.tone-class
              | add
            strong
              | text
            | in
            span#template-id
              | Slim
    aside role="complementary"
      form role="search" / Role for search boxes only
    footer role="contentinfo" / Role for main footer only
```

Since landmark roles are essential for keyboard navigation we included the most
basic set in the code sample above.

Although there is apparent redundancy for `main` and `form` it's actually meant to
reinforcing semantics. For instance, `main` role
<q cite="https://www.w3.org/TR/wai-aria/roles#main">is a non-obtrusive alternative to "skip to main content" links</q>.
It's not exclusive of the `main` html element.

### Images

- Only add relevant alternative info. eg. don't describe logos.
- The `alt` attribute only works on `img`, `area`, `input`.
- Use `aria-label` to add alternative info anywhere.
- Set purely decorative images as CSS backgrounds.
- Use `aria-hidden` attribute for decorations such as icon fonts.
- When referring to images from the text we can use `figure`, and
  optionally `figcaption` to describe the diagram.

#### alt text

`alt` text is a description of an image for those who can't see it. Hence,

- Describe the image.
  + don't SEO it.
  + no place for attributions.
- `alt` text depends on context.
- Keep it short.
- Don't start with 'image of', or 'photo of'. Screen readers already say it.
- End with a period.

Even when no `alt` text is needed we need to use it with an empty attribute for:

- repeated images. eg. profile pictures in a feed.
- icons with text labels.
- linked images with caption. ie. newspaper style.

Invoke purely decorative images, such as backgrounds, in CSS to avoid using `alt`
altogether.

SVG images can be called using an `img` tag, in which case we can use the `alt`
text to describe it. When, for reasons, we need to embed the image directly into
and `svg` element we'll need to use `role="img"`, and `aria-label="alt text here"`
to make it accessible.


### Tables

- Use CSS grid instead of tables for tabular layouts.
- Avoid nesting tables.
- `caption` tables to associate them with their descriptions.
- Set header scope.
- Use proportional (`rem` or `em`) rather than absolute sizing.
- `thead`, `tfoot`, and `tbody` group cells semantically.
- `tr`, `th`, `td` make data navigation easier.

```slim
table
  caption
    | Shopping List
  thead
    tr
      th scope="col"
        | Description
      th scope="col"
        | Price
      th scope="col"
        | Quantity
  tbody
    tr
      th scope="row"
        | Phone
      td 5
      td 1
    tr
      th scope="row"
        | Computer
      td 8
      td 2
  tfoot
    tr
      th scope="row"
        | Total
      td 21
      td 3
```

### Forms

- Ensure they're keyboard accessible.
- Organize and label fields clearly.
- `fieldset`, `section`, and `div` delimit form space.
- Associated elements have matching `id`, and `for`.
- Associate `label`s extend selection area.
- Use `fieldset` with `legend` for specificity.
- Avoid `<select multiple>` menus.
- Avoid empty:
  + `value` attributes.
  + `button` contents.
- Don't replace `label`s with `placeholder`s.

A few ways we can associate form elements:

```slim
form id="pizza-order" role="form"
  fieldset
    legend
      | Toppings:
    input id="ham" type="checkbox" name="toppings" value="ham"
    label for="ham"
      | Ham
    input id="pepperoni" type="checkbox" name="toppings" value="pepperoni"
    label for="pepperoni"
      | Pepperoni
    input id="mushrooms" type="checkbox" name="toppings" value="mushrooms"
    label for="mushrooms"
      | Mushrooms
    input id="olives" type="checkbox" name="toppings" value="olives"
    label for="olives"
      | Olives

  label for="city"
    | Choose your delivery city
  select id="city" name="delivery-city"
    optgroup label="Asia"
      option value="HK"
        | Hong Kong
      option value="TK"
        | Tokyo
    optgroup label="Europe"
      option value="AM"
        | Amsterdam
      option value="BA"
        | Barcelona
    optgroup label="North America"
      option value="MX"
        | Mexico City
      option value="NY"
        | New York
    optgroup label="South America"
      option value="SP"
        | Sao Paulo

  input for="pizza-order" type="submit" name="pizza-order" value="Order"
  input for="pizza-order" type="reset" name="cancel" value="Cancel"
```

#### Inputs

**Note**:
Most browsers support `autocomplete` for various `input` elements. While this may
arguably be good from an accessibility standpoint, keep in mind it isn't from a
privacy, and security perspective.

##### Common Attributes

Mandatory form fields:

```slim
input[type="text" required]
```

##### Types

*Check out [W3 School's input types list](https://www.w3schools.com/html/html_form_input_types.asp){:rel="nofollow noreferrer noopener"}
for a the complete set of input types and attributes.*

###### Text & Patterns

We can add simple validation patterns to `type="text"` inputs. Most common browser
style failure to match the require pattern.

```slim
input[
  type="text"
  pattern="^(\d{3}-?\d{4})$"
]
```

These validations are meant to improve usability, not security. If security
is a concern we must always do it at the server level.

Some common patterns are:

```slim
// Generic text. No special characters
input[
  type="text"
  pattern="[a-zA-Z0-9]+"
]

// Username. 2-20 characters long
input[
  type="username"
  pattern="^[a-zA-Z][a-zA-Z0-9-_\.]{1,20}$"
]

// Password. Upper, lower cases, numbers, special characters, min 9 chars
input[
  type="password"
  pattern="(?=^.{8,}$)((?=.*\d)|(?=.*\W+))(?![.\n])(?=.*[A-Z])(?=.*[a-z]).*$"
]
```

For security reasons, never style `type="password"` other than when missing
in register forms.

### Web Linking

Web pages are linked through `a` elements. We can tell user agents such as bots,
browsers, and screen readers, how a website relates to ours through the `rel`
attribute. Here's a basic list. (`href`s omitted for simplicity)

#### Access

```slim
a rel="contents" / as in TOC
a rel="home"
a rel="first"
a rel="last"
a rel="prev"
a rel="glossary"
a rel="help"
a rel="alternate" / page's alt delivery mechanism eg. Atom feed.
```

#### Attributions

```slim
a rel="author"
a rel="license content-license" / link to data license
a rel="content-repository" / link to data store
a rel="code-license"
a rel="code-repository"
```

#### Privacy

```slim
a rel="noopener noreferrer"
a rel="nofollow"
a rel="privacy-policy"
a rel="terms-of-service"
```

The first setting,`noopener` `noreferrer`, helps protect users from [tabnabbing attacks](https://www.jitbit.com/alexblog/256-targetblank---the-most-underestimated-vulnerability-ever/){:rel="nofollow noreferrer noopener"}
without damaging the site's [SEO](https://blog.templatetoaster.com/rel-noreferrer-noopener-seo/){:rel="nofollow noreferrer noopener"}:

- Use `nofollow` for:
  + Sites whose content we usually don't refer to.
  + Non-endorsed sites.
  + Paid referrals.

### Main Nav

Considering semantic HTML, landmark roles, and web linking, a simple main
navigation bar could look like:

```slim
nav role="navigation"
  ul
    li
      a[
        rel="noopener noreferrer"
        href="https://avoid.empty.href.com"
      ]
        | 'a' elements shouldn't be empty, ever.
    li
      a[
        rel="nofollow noopener noreferrer"
        href="https://non.endorsed.page.com"
      ]
        | Profile in unrelated site

    li
      a[
        rel="noopener noreferrer"
        href="/about"
      ]
        | noopener noreferrer are for visitors' benefit
```

The use of `ul`, and `li` elements is merely as an example. Nowadays, is easy to
control navigation bar's layout with
[CSS grid](https://css-tricks.com/snippets/css/complete-guide-grid/){:rel="nofollow noreferrer noopener"}
or
[flexbox](https://css-tricks.com/snippets/css/a-guide-to-flexbox/){:rel="nofollow noreferrer noopener"},
if needed. That's beyond the scope of this cheat sheet, though.

## Fancy Meets Accessible

### Dynamic Content

Hide anything visually, as well as from screen readers, and other user agents:

```css
.a { visibility: hidden }
.b { display: hidden }
.c { display: none }
```

Avoid hidding HTML elements through the `hidden` attribute. It creates a dependency
on ECMAScript (JS), which not all user might have access to, specially on mobiles.

Hide anything only visually by:

```css
.tucked-away {
  position: absolute !important;
  height: 1px;
  width: 1px;
  overflow: hidden;
  clip: rect(1px, 1px, 1px, 1px);
}

.camouflaged {
  position: absolute !important;
  height: 1px;
  width: 1px;
  overflow: hidden;
  opacity:0;
}

.unapparent {
  position: absolute;
  height: 1px;
  width: 1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  border: 0;
  margin: -1px;
  padding: 0;
}
```

- Ensure hidden content announces itself upon display.
- When necessary, ensure dynamic content is keyboard accessible.

### Colors

- Avoid using only color to convey information ie. single green/red dot.
- Set the contrast ratio between elements so everyone can distinguish them.
  + Rely on contrast ratio calculators.

Online contrast ratio checkers:

- [Colorable](https://colorable.jxnblk.com/){:rel="nofollow noopener noreferrer"}.
Applies color combo.
- [Contrast Ratio](https://leaverou.github.io/contrast-ratio/){:rel="nofollow noopener noreferrer"}
Displays colors side by side.

Accessible color combinations:

- [Accessible Color Pallete Builder](https://toolness.github.io/accessible-color-matrix/){:rel="nofollow noreferrer noopener"}
- [Colors A11y](https://clrs.cc/a11y/){:rel="nofollow noopener noreferrer"}
  + [Colors](https://clrs.cc/){:rel="nofollow noopener noreferrer"}. For the actual
    color hex numbers.
- [BassCss' Color Combos](https://basscss.com/v7/docs/reference/color-combinations/){:rel="nofollow noopener noreferrer"}

## Resources

### Tools & Code

- [WAI "bad" demo](https://www.w3.org/WAI/demos/bad/){:rel="nofollow noopener noreferrer"}
- [Interactive POUR Recommendations](https://code.viget.com/interactive-wcag/#responsibility=&level=aaa){:rel="nofollow noreferrer noopener"}
- [Accessible Color Pallete Builder](https://toolness.github.io/accessible-color-matrix/){:rel="nofollow noreferrer noopener"}
- [WAVE browser extensions](https://wave.webaim.org/extension/){:rel="nofollow noopener noreferrer"}
- [Tota11y.js](https://khan.github.io/tota11y/){:rel="nofollow noopener noreferrer"}
- [Tenon Accessibility Analyzer](https://tenon.io/){:rel="nofollow noopener noreferrer"}
- [The A11y Project](https://a11yproject.com/){:rel="nofollow noopener noreferrer"}
- [CSS Grid](https://css-tricks.com/snippets/css/complete-guide-grid/){:rel="nofollow noreferrer noopener"}
- [Flexbox](https://css-tricks.com/snippets/css/a-guide-to-flexbox/){:rel="nofollow noreferrer noopener"}
- [18F Accessibility Checklist](https://accessibility.18f.gov/checklist/){:rel="nofollow noreferrer noopener"}
- [Colorable](https://colorable.jxnblk.com/){:rel="nofollow noopener noreferrer"}.
- [Contrast Ratio Calculator](https://leaverou.github.io/contrast-ratio/){:rel="nofollow noopener noreferrer"}
- [Contrast Checker](https://webaim.org/resources/contrastchecker/){:rel="nofollow noopener noreferrer"}.
- [Colors](https://clrs.cc/){:rel="nofollow noopener noreferrer"}.
- [Colors A11y](https://clrs.cc/a11y/){:rel="nofollow noopener noreferrer"}.
- [BassCss' Color Combos](https://basscss.com/v7/docs/reference/color-combinations/){:rel="nofollow noopener noreferrer"}


### References

- [Creating Accessible Forms](https://webaim.org/techniques/forms/){:rel="nofollow noopener noreferrer"}
- [WebAIM's WCAG 2.0 Checklist](https://webaim.org/standards/wcag/checklist/){:rel="nofollow noopener noreferrer"}
- [the-accessibility-cheatsheet](https://bitsofco.de/the-accessibility-cheatsheet/){:rel="nofollow noopener noreferrer"}
- [Creating Accessible Tables](https://webaim.org/techniques/tables/data){:rel="nofollow noopener noreferrer"}
- [HTML For Icon Font Usage](https://css-tricks.com/html-for-icon-font-usage/){:rel="nofollow noopener noreferrer"}
- [Text For Screen Readers Only](https://www.coolfields.co.uk/2016/05/text-for-screen-readers-only-updated/){:rel="nofollow noopener noreferrer"}
- [autofill-what-web-devs-should-know-but-dont](https://cloudfour.com/thinks/autofill-what-web-devs-should-know-but-dont/){:rel="nofollow noopener noreferrer"}
- [W3 School's input types list](https://www.w3schools.com/html/html_form_input_types.asp){:rel="nofollow noreferrer noopener"}
- [Vox Accessibility Guidelines](https://accessibility.voxmedia.com/){:rel="nofollow noopener noreferrer"}
- [Aria Web Standards](https://www.hongkiat.com/blog/aria-web-standards/){:rel="nofollow noopener noreferrer"}
- [Using Aria](https://www.w3.org/TR/using-aria/){:rel="nofollow noopener noreferrer"}
- [Aria Landmark Roles](https://www.w3.org/TR/wai-aria/roles#landmark_roles){:rel="nofollow noopener noreferrer"}
- [Accessibility Best Practices](https://www.webaccessibility.com/best_practices.php){:rel="nofollow noopener noreferrer"}
- [HTML Language Code Reference](https://www.w3schools.com/tags/ref_language_codes.asp){:rel="nofollow noreferrer noopener"}
- [Understanding Microsoft's meta-tag](https://blogs.msdn.microsoft.com/asiatech/2016/09/20/ie11-migration-guide-understanding-browser-mode-document-mode-user-agent-and-x-ua-compatible/){:rel="nofollow noopener noreferrer"}
- [Web Linking (RFC5988bis)](https://mnot.github.io/I-D/rfc5988bis/){:rel="nofollow noopener noreferrer"}
Updated version of RFC5988 (referenced at the top as obsolete).
- [Creating Accessible Menus](https://simpleprimate.com/blog/nav-1){:rel="nofollow noreferrer noopener"}
- [tabnabbing attacks](https://www.jitbit.com/alexblog/256-targetblank---the-most-underestimated-vulnerability-ever/){:rel="nofollow noreferrer noopener"}
- [rel="noreferrer noopener" SEO Issues?](https://blog.templatetoaster.com/rel-noreferrer-noopener-seo/){:rel="nofollow noreferrer noopener"}
- [Link Relation Types](https://www.iana.org/assignments/link-relations/link-relations.xhtml){:rel="nofollow noreferrer noopener"}
- [HTML5 Link Type Extensions](https://microformats.org/wiki/existing-rel-values#HTML5_link_type_extensions){:rel="nofollow noreferrer noopener"}
- [7 Habits of Highly Effective Media Queries](https://bradfrost.com/blog/post/7-habits-of-highly-effective-media-queries/){:rel="nofollow noreferrer noopener"}
- [Media Queries: Width vs Device Width](https://www.sitepoint.com/media-queries-width-vs-device-width/){:rel="nofollow noreferrer noopener"}
- [Hiding Content for Accessibility](https://snook.ca/archives/html_and_css/hiding-content-for-accessibility){:rel="nofollow noreferrer noopener"}
- [alt text](https://axesslab.com/alt-texts/){:rel="nofollow noreferrer noopener"}
- [MDN Learn Accessibility](https://developer.mozilla.org/en-US/docs/Learn/Accessibility){:rel="nofollow noreferrer noopener"}
- [W3 Web Accessibility Tutorial](https://www.w3.org/WAI/tutorials/){:rel="nofollow noreferrer noopener"}
- [Tips on Developing for Web Accessibility](https://www.w3.org/WAI/gettingstarted/tips/developing.html){:rel="nofollow noreferrer noopener"}
- [Using the HTML title attribute](https://developer.paciellogroup.com/blog/2013/01/using-the-html-title-attribute-updated/){:rel="nofollow noreferrer noopener"}
